﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;

namespace NDF.XnaLibrary.Models
{
	/// <summary>
	/// Base class for holding information about a single model and properties about the way the model should be drawn.
	/// </summary>
	public abstract class GameModel
	{
		#region Fields
		
		protected string mName;
		protected bool mIsLoaded;
		protected Vector3 mScale;
		protected Model mModel;
		protected Matrix[] mModelBoneTransforms;
		protected Effect mEffect;

		#endregion

		#region Constructors

		protected GameModel(Model model) :
			this(model, null, string.Empty)
		{}

		protected GameModel(Model model, string name) :
			this(model, null, name)
		{}

		protected GameModel(Model model, Effect effect) :
			this(model, effect, string.Empty)
		{ }

		protected GameModel(Model model, Effect effect, string modelName)
		{
			this.mName = modelName;
			this.Model = model;
			this.mScale = Vector3.One;

			if (effect != null)
			{
				this.mEffect = effect;
				SaveEffectChanges();
			}

		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the name of the character.
		/// </summary>
		public string Name
		{
			get { return mName; }
			set { mName = value; }
		}

		/// <summary>
		/// Gets or sets the scale of the model.
		/// </summary>
		public Vector3 Scale
		{
			get { return mScale; }
			set { mScale = value; }
		}

		/// <summary>
		/// Gets or sets the model.
		/// </summary>
		public virtual Model Model
		{
			get { return mModel; }
			set 
			{
				mModel = value;
				mModelBoneTransforms = new Matrix[mModel.Bones.Count];
				mModel.CopyAbsoluteBoneTransformsTo(mModelBoneTransforms);

				mIsLoaded = true;
			}
		}

		/// <summary>
		/// Gets or sets the effect of the game model.
		/// </summary>
		public virtual Effect Effect
		{
			get { return mEffect; }
			set { mEffect = value; }
		}

		/// <summary>
		/// Shows whether the model has been loaded.
		/// <para>Before a model has been loaded you cannot set the effect of the character or draw it.</para>
		/// </summary>
		public bool IsLoaded
		{
			get { return mIsLoaded; }
		}

		#endregion

		#region Methods

		/// <summary>
		/// Copies the properties of the given BasicEffects
		/// </summary>
		/// <param name="target">The effect to copy to</param>
		/// <param name="source">The effect to copy from</param>
		protected static void CopyEffectProperties(BasicEffect target, BasicEffect source)
		{
			target.Alpha = source.Alpha;
			target.AmbientLightColor = source.AmbientLightColor;
			target.DiffuseColor = source.DiffuseColor;
			target.EmissiveColor = source.EmissiveColor;
			target.PreferPerPixelLighting = source.PreferPerPixelLighting;
			target.SpecularColor = source.SpecularColor;
			target.SpecularPower = source.SpecularPower;
			target.VertexColorEnabled = source.VertexColorEnabled;

			if (source.FogEnabled)
			{
				target.FogEnabled = true;
				target.FogColor = source.FogColor;
				target.FogEnd = source.FogEnd;
				target.FogStart = source.FogStart;
			}

			if (source.DirectionalLight0.Enabled)
			{
				target.DirectionalLight0.Enabled = true;
				target.DirectionalLight0.Direction = source.DirectionalLight0.Direction;
				target.DirectionalLight0.SpecularColor = source.DirectionalLight0.SpecularColor;
				target.DirectionalLight0.DiffuseColor = source.DirectionalLight0.DiffuseColor;
			}
			if (source.DirectionalLight1.Enabled)
			{
				target.DirectionalLight1.Enabled = true;
				target.DirectionalLight1.Direction = source.DirectionalLight1.Direction;
				target.DirectionalLight1.SpecularColor = source.DirectionalLight1.SpecularColor;
				target.DirectionalLight1.DiffuseColor = source.DirectionalLight1.DiffuseColor;
			}
			if (source.DirectionalLight2.Enabled)
			{
				target.DirectionalLight2.Enabled = true;
				target.DirectionalLight2.Direction = source.DirectionalLight2.Direction;
				target.DirectionalLight2.SpecularColor = source.DirectionalLight2.SpecularColor;
				target.DirectionalLight2.DiffuseColor = source.DirectionalLight2.DiffuseColor;
			}
		}

		/// <summary>
		/// Copies the properties of the given SkinnedEffects
		/// </summary>
		/// <param name="target">The effect to copy to</param>
		/// <param name="source">The effect to copy from</param>
		protected static void CopyEffectProperties(SkinnedEffect target, SkinnedEffect source)
		{
			target.Alpha = source.Alpha;
			target.AmbientLightColor = source.AmbientLightColor;
			target.DiffuseColor = source.DiffuseColor;
			target.EmissiveColor = source.EmissiveColor;
			target.PreferPerPixelLighting = source.PreferPerPixelLighting;
			target.SpecularColor = source.SpecularColor;
			target.SpecularPower = source.SpecularPower;
			target.WeightsPerVertex = source.WeightsPerVertex;

			if (source.FogEnabled)
			{
				target.FogEnabled = true;
				target.FogColor = source.FogColor;
				target.FogEnd = source.FogEnd;
				target.FogStart = source.FogStart;
			}

			if (source.DirectionalLight0.Enabled)
			{
				target.DirectionalLight0.Enabled = true;
				target.DirectionalLight0.Direction = source.DirectionalLight0.Direction;
				target.DirectionalLight0.SpecularColor = source.DirectionalLight0.SpecularColor;
				target.DirectionalLight0.DiffuseColor = source.DirectionalLight0.DiffuseColor;
			}
			if (source.DirectionalLight1.Enabled)
			{
				target.DirectionalLight1.Enabled = true;
				target.DirectionalLight1.Direction = source.DirectionalLight1.Direction;
				target.DirectionalLight1.SpecularColor = source.DirectionalLight1.SpecularColor;
				target.DirectionalLight1.DiffuseColor = source.DirectionalLight1.DiffuseColor;
			}
			if (source.DirectionalLight2.Enabled)
			{
				target.DirectionalLight2.Enabled = true;
				target.DirectionalLight2.Direction = source.DirectionalLight2.Direction;
				target.DirectionalLight2.SpecularColor = source.DirectionalLight2.SpecularColor;
				target.DirectionalLight2.DiffuseColor = source.DirectionalLight2.DiffuseColor;
			}
		}

		/// <summary>
		/// Copies the properties of the given NDF-Effects.
		/// </summary>
		/// <param name="target">The effect to copy to.</param>
		/// <param name="source">The effect to copy from.</param>
		protected static void CopyEffectProperties(Effect target, Effect source)
		{
			target.Parameters["LightViewProjection"].SetValue(source.Parameters["LightViewProjection"].GetValueMatrix());
			target.Parameters["PointLight0Position"].SetValue(source.Parameters["PointLight0Position"].GetValueVector3());
			target.Parameters["PointLight0Color"].SetValue(source.Parameters["PointLight0Color"].GetValueVector4());
			target.Parameters["PointLight0Range"].SetValue(source.Parameters["PointLight0Range"].GetValueSingle());
			target.Parameters["AmbientColor"].SetValue(source.Parameters["AmbientColor"].GetValueVector4());
			target.Parameters["Attenuation2"].SetValue(source.Parameters["Attenuation2"].GetValueSingle());
			target.Parameters["Attenuation1"].SetValue(source.Parameters["Attenuation1"].GetValueSingle());
			target.Parameters["Attenuation0"].SetValue(source.Parameters["Attenuation0"].GetValueSingle());
		}

		/// <summary>
		/// Saves the changes you`ve made to the gamemodel`s effect. 
		/// <para>Call this method each time you modify the character`s effect.</para>
		/// <para>Load the character model prior to using this method.</para>
		/// <exception cref="InvalidOperationException"></exception>
		/// </summary>
		public abstract void SaveEffectChanges();

		/// <summary>
		/// Saves the changes you`ve made to the gamemodel`s effect. 
		/// <para>Call this method each time you modify the character`s effect.</para>
		/// <para>Load the character model prior to using this method.</para>
		/// <param name="effect">The BasicEffect to use.</param>
		/// <exception cref="InvalidOperationException"></exception>
		/// </summary>
		protected void SaveEffectChanges(BasicEffect effect)
		{
			if (!mIsLoaded)
			{
				throw new InvalidOperationException("Cant save changes to the effect prior to loading the model. Load the model first.");
			}

			foreach (ModelMesh mesh in mModel.Meshes)
			{
				foreach (BasicEffect meshEffect in mesh.Effects)
				{
					CopyEffectProperties(meshEffect, effect);
				}
			}
		}

		/// <summary>
		/// Saves the changes you`ve made to the gamemodel`s effect. 
		/// <para>Call this method each time you modify the character`s effect.</para>
		/// <para>Load the character model prior to using this method.</para>
		/// <param name="effect">The SkinnedEffect to use.</param>
		/// <exception cref="InvalidOperationException"></exception>
		/// </summary>
		protected void SaveEffectChanges(SkinnedEffect effect)
		{
			if (!mIsLoaded)
			{
				throw new InvalidOperationException("Cant save changes to the effect prior to loading the model. Load the model first.");
			}

			foreach (ModelMesh mesh in mModel.Meshes)
			{
				foreach (SkinnedEffect meshEffect in mesh.Effects)
				{
					CopyEffectProperties(meshEffect, effect);
				}
			}
		}

		/// <summary>
		/// Saves the changes you`ve made to the gamemodel`s effect. 
		/// <para>Call this method each time you modify the character`s effect.</para>
		/// <para>Load the character model prior to using this method.</para>
		/// <param name="effect">The SkinnedEffect to use.</param>
		/// <exception cref="InvalidOperationException"></exception>
		/// </summary>
		protected void SaveEffectChanges(Effect effect)
		{
			if (!mIsLoaded)
			{
				throw new InvalidOperationException("Cant save changes to the effect prior to loading the model. Load the model first.");
			}

			foreach (ModelMesh mesh in mModel.Meshes)
			{
				foreach (Effect meshEffect in mesh.Effects)
				{
					CopyEffectProperties(meshEffect, effect);
				}
			}
		}

		#endregion
	}
}
